/**
 * 此为验证码生成库，用于生成bmp格式的验证码，非常小巧
 * 使用示例

  // 初始化验证码实例
  const captcha = new Captcha({
    level: 2, // 干扰等级 1 低 2 中 3 高 4 极高 推荐使用2或3即可
  });
  // 绘制验证码，得到验证码text和buffer和base64，前端可直接 <image :src="base64" /> 显示验证码
  const { text, buffer, base64 } = captcha.draw();

 */
class Captcha {
  constructor (data = {}) {
    let { level = 2, ...config } = data
    const width = 300
    const height = 100
    this.width = width
    this.height = height

    const textColor = [Math.floor(Math.random() * 256), Math.floor(Math.random() * 256), Math.floor(Math.random() * 256)]

    if ([1, 2, 3, 4].indexOf(level) === -1) {
      level = 2
    }

    const realityConfig = {}

    if (level === 1) {
      Object.assign(realityConfig, {
        lineWidth: 5,
        textColor: textColor,
        textLength: 4,
        lineOffset: 0,
        background: [255, 250, 232],
        randomLineNum: 5
      }, config)
    } else if (level === 2) {
      Object.assign(realityConfig, {
        lineWidth: 5,
        textColor: textColor,
        textLength: 4,
        lineOffset: 0,
        background: [255, 250, 232],
        randomLineNum: 10
      }, config)
    } else if (level === 3) {
      Object.assign(realityConfig, {
        lineWidth: 5,
        textColor: textColor,
        textLength: 4,
        lineOffset: 1,
        background: [255, 250, 232],
        randomLineNum: 15
      }, config)
    } else if (level === 4) {
      Object.assign(realityConfig, {
        lineWidth: 5,
        textColor: textColor,
        textLength: 4,
        lineOffset: 1,
        background: [255, 250, 232],
        randomLineNum: 15
      }, config)
      realityConfig.textColor = function () {
        return [Math.floor(Math.random() * 256), Math.floor(Math.random() * 256), Math.floor(Math.random() * 256)]
      }
    }
    this.config = realityConfig
    this.data = Buffer.alloc(3 * width * height)
  }

  setPixel (x, y, color) {
    const index = (y * this.width + x) * 3
    this.data[index] = color.b
    this.data[index + 1] = color.g
    this.data[index + 2] = color.r
  }

  getFileBuffer (filename) {
    const fileHeaderSize = 14
    const infoHeaderSize = 40
    const fileSize = fileHeaderSize + infoHeaderSize + this.data.length
    const fileBuffer = Buffer.alloc(fileSize)

    // BMP 文件头
    fileBuffer.write('BM', 0)
    fileBuffer.writeUInt32LE(fileSize, 2)
    fileBuffer.writeUInt32LE(0, 6) // 保留字段
    fileBuffer.writeUInt32LE(fileHeaderSize + infoHeaderSize, 10)

    // BMP 信息头
    fileBuffer.writeUInt32LE(infoHeaderSize, 14) // 信息头大小
    fileBuffer.writeInt32LE(this.width, 18)
    fileBuffer.writeInt32LE(-this.height, 22) // 高度为负表示顶部到底部
    fileBuffer.writeUInt16LE(1, 26) // 颜色平面数
    fileBuffer.writeUInt16LE(24, 28) // 每像素位数
    fileBuffer.writeUInt32LE(0, 30) // 压缩方式
    fileBuffer.writeUInt32LE(this.data.length, 34) // 图像数据大小

    // 写入像素数据
    this.data.copy(fileBuffer, fileHeaderSize + infoHeaderSize)

    return fileBuffer
  }

  // 设置整个图片的背景色
  setImageBackground (r, g, b) {
    // 设置像素值，这里将所有像素设置为红色
    for (let x = 0; x < this.width; x++) {
      for (let y = 0; y < this.height; y++) {
        this.setPixel(x, y, { r, g, b })
      }
    }
  }

  // 设置整个图片的随机背景色
  setImageRandomBackground (blockSize) {
    for (let x = 0; x < this.width; x += blockSize) {
      for (let y = 0; y < this.height; y += blockSize) {
        const r = Math.floor(Math.random() * 256)
        const g = Math.floor(Math.random() * 256)
        const b = Math.floor(Math.random() * 256)
        // 在块内设置颜色
        for (let i = 0; i < blockSize; i++) {
          for (let j = 0; j < blockSize; j++) {
            if (x + i < this.width && y + j < this.height) {
              this.setPixel(x + i, y + j, { r, g, b })
            }
          }
        }
      }
    }
  }

  // 绘制验证码
  draw (data) {
    const text = data && data.text ? data.text : this.randomString(this.config.textLength)
    // 设置背景色
    this.setImageBackground(...this.config.background)

    for (let i = 0; i < text.length; i++) {
      const item = text[i]
      this.drawText(item, i)
    }
    if (this.config.randomLineNum) {
      // 绘制随机线条
      this.drawRandomLinesWithVaryingWidth(this.config.randomLineNum)
    }
    const buffer = this.getFileBuffer()
    // 生成base64
    const base64 = 'data:image/bmp;base64,' + buffer.toString('base64')
    return {
      text,
      buffer,
      base64
    }
  }

  randomString (len) {
    len = len || 32
    var $chars = '0123456789'
    var maxPos = $chars.length
    var str = ''
    var i
    for (i = 0; i < len; i++) {
      str += $chars.charAt(Math.floor(Math.random() * maxPos))
    }
    return str
  }

  // 绘制0-9的数字
  drawText (text, n) {
    const width = this.config.lineWidth
    const offset = this.getTextOffset(n)
    if (text === '1') {
      this.drawLine(1, 25, 25, 50 + width, offset)
    } else if (text === '2') {
      this.drawLine(0, 5, 25, 40, offset)
      this.drawLine(1, 45 - width, 25, 25 + width, offset)
      this.drawLine(0, 5, 50, 40, offset)
      this.drawLine(1, 5, 50, 25 + width, offset)
      this.drawLine(0, 5, 75, 40, offset)
    } else if (text === '3') {
      this.drawLine(0, 5, 25, 40, offset)
      this.drawLine(1, 45 - width, 25, 25 + width, offset)
      this.drawLine(0, 5, 50, 40, offset)
      this.drawLine(1, 45 - width, 50, 25 + width, offset)
      this.drawLine(0, 5, 75, 40, offset)
    } else if (text === '4') {
      this.drawLine(1, 5, 25, 25 + width, offset)
      this.drawLine(1, 45 - width, 25, 25 + width, offset)
      this.drawLine(0, 5, 50, 40, offset)
      this.drawLine(1, 45 - width, 50, 25 + width, offset)
    } else if (text === '5') {
      this.drawLine(0, 5, 25, 40, offset)
      this.drawLine(1, 5, 25, 25 + width, offset)
      this.drawLine(0, 5, 50, 40, offset)
      this.drawLine(1, 45 - width, 50, 25 + width, offset)
      this.drawLine(0, 5, 75, 40, offset)
    } else if (text === '6') {
      this.drawLine(0, 5, 25, 40, offset)
      this.drawLine(1, 5, 25, 25 + width, offset)
      this.drawLine(0, 5, 50, 40, offset)
      this.drawLine(1, 45 - width, 50, 25 + width, offset)
      this.drawLine(0, 5, 75, 40, offset)
      this.drawLine(1, 5, 50, 25 + width, offset)
    } else if (text === '7') {
      this.drawLine(0, 5, 25, 40, offset)
      this.drawLine(1, 45 - width, 25, 25 + width, offset)
      this.drawLine(1, 45 - width, 50, 25 + width, offset)
    } else if (text === '8') {
      this.drawLine(0, 5, 25, 40, offset)
      this.drawLine(0, 5, 50, 40, offset)
      this.drawLine(0, 5, 75, 40, offset)
      this.drawLine(1, 5, 25, 25 + width, offset)
      this.drawLine(1, 5, 50, 25 + width, offset)
      this.drawLine(1, 45 - width, 25, 25 + width, offset)
      this.drawLine(1, 45 - width, 50, 25 + width, offset)
    } else if (text === '9') {
      this.drawLine(0, 5, 25, 40, offset)
      this.drawLine(0, 5, 50, 40, offset)
      this.drawLine(0, 5, 75, 40, offset)
      this.drawLine(1, 5, 25, 25 + width, offset)
      this.drawLine(1, 45 - width, 25, 25 + width, offset)
      this.drawLine(1, 45 - width, 50, 25 + width, offset)
    } else if (text === '0') {
      this.drawLine(0, 5, 25, 40, offset)
      this.drawLine(0, 5, 75, 40, offset)
      this.drawLine(1, 5, 25, 25 + width, offset)
      this.drawLine(1, 5, 50, 25 + width, offset)
      this.drawLine(1, 45 - width, 25, 25 + width, offset)
      this.drawLine(1, 45 - width, 50, 25 + width, offset)
    }
  }

  /**
   * 绘制线条
   * @param {number} type 0 横线 1 竖线
   * @param {number} x 起始坐标x
   * @param {number} y 起始坐标y
   * @param {number} length 线的长度
   * @param {number} offset 起始坐标的偏移量, 默认 { x: 0, y: 0 }
   * @param {number} width 线的宽度, 默认5
   */
  drawLine (type, x, y, length, offset = { x: 0, y: 0 }) {
    const width = this.config.lineWidth
    const color = typeof this.config.textColor === 'function' ? this.config.textColor() : this.config.textColor
    x += offset.x
    y += offset.y

    if (type === 0) {
      // 画横线, 宽度是length, 线高度是width,
      for (let i = 0; i < width; i++) {
        for (let j = 0; j < length; j++) {
          const random = this.getRandomOffset()
          // 设置像素值
          this.setPixel(x + j + random, y + i + random, { r: color[0], g: color[1], b: color[2] })
        }
      }
    } else if (type === 1) {
      // 画竖线, 宽度是width, 线高度是length,
      for (let i = 0; i < width; i++) {
        for (let j = 0; j < length; j++) {
          const random = this.getRandomOffset()
          // 设置像素值
          this.setPixel(x + i + random, y + j + random, { r: color[0], g: color[1], b: color[2] })
        }
      }
    }
  }

  // 获得随机偏移量
  getRandomOffset () {
    if (this.config.lineOffset === 1) {
      return Math.floor(Math.random() * 3) - 1 // 程度：小
    } else if (this.config.lineOffset === 2) {
      return Math.floor(Math.random() * 6) - 3 // 程度：中
    } else if (this.config.lineOffset >= 3) {
      return Math.floor(Math.random() * 11) - 6 // 程度：大
    } else {
      return 0
    }
  }

  // 获得文本绘制偏移量
  getTextOffset (n = 0) {
    const textLength = this.config.textLength
    const padding = Math.round((300 - 50 * textLength) / 2 / textLength)
    return {
      x: n * (300 / this.config.textLength) + padding,
      y: 0
    }
  }

  /**
   * 绘制多条随机线条
   * @param {number} numLines 要绘制的随机线条数量
   */
  drawRandomLinesWithVaryingWidth (numLines) {
    const width = this.width
    const height = this.height

    for (let i = 0; i < numLines; i++) {
      const x1 = Math.floor(Math.random() * width)
      const y1 = Math.floor(Math.random() * height)
      const x2 = Math.floor(Math.random() * width)
      const y2 = Math.floor(Math.random() * height)

      const red = Math.floor(Math.random() * 256)
      const green = Math.floor(Math.random() * 256)
      const blue = Math.floor(Math.random() * 256)

      const lineWidth = Math.floor(Math.random() * 2) + 2

      // 绘制线条
      this.drawLineWithWidth(x1, y1, x2, y2, { r: red, g: green, b: blue }, lineWidth)
    }
  }

  /**
   * 绘制一条线条
   * @param {number} x1 起始坐标x
   * @param {number} y1 起始坐标y
   * @param {number} x2 结束坐标x
   * @param {number} y2 结束坐标y
   * @param {object} color 颜色对象 { r, g, b }
   * @param {number} width 线条宽度
   */
  drawLineWithWidth (x1, y1, x2, y2, color, width) {
    const dx = Math.abs(x2 - x1)
    const dy = Math.abs(y2 - y1)
    const sx = (x1 < x2) ? 1 : -1
    const sy = (y1 < y2) ? 1 : -1

    let err = dx - dy

    while (true) {
      for (let i = 0; i < width; i++) {
        this.setPixel(x1, y1 + i, color)
      }

      if (x1 === x2 && y1 === y2) {
        break
      }

      const e2 = 2 * err
      if (e2 > -dy) {
        err -= dy
        x1 += sx
      }
      if (e2 < dx) {
        err += dx
        y1 += sy
      }
    }
  }
}

module.exports = Captcha
